#include "types.h"
#include "stat.h"
#include "user.h"

/* Possible states of a thread; */
#define FREE 0x0
#define RUNNING 0x1
#define RUNNABLE 0x2

#define STACK_SIZE 8192
#define MAX_THREAD 4

typedef struct thread thread_t, *thread_p;
typedef struct mutex mutex_t, *mutex_p;

struct thread
{
    int sp;                 /* saved stack pointer */
    char stack[STACK_SIZE]; /* the thread's stack */
    int state;              /* FREE, RUNNING, RUNNABLE */
};
static thread_t all_thread[MAX_THREAD];
thread_p current_thread;
thread_p next_thread;
extern void thread_switch(void);

void thread_init(void)
{
    // main() is thread 0, which will make the first invocation to
    // thread_schedule().  it needs a stack so that the first thread_switch() can
    // save thread 0's state.  thread_schedule() won't run the main thread ever
    // again, because its state is set to RUNNING, and thread_schedule() selects
    // a RUNNABLE thread.
    current_thread = &all_thread[0];
    current_thread->state = RUNNING;
}

static void
thread_schedule(void)
{
    thread_p t;

    /* Find another runnable thread. */
    next_thread = 0;
    for (t = all_thread; t < all_thread + MAX_THREAD; t++)
    {
        if (t->state == RUNNABLE && t != current_thread)
        {
            next_thread = t;
            break;
        }
    }

    if (t >= all_thread + MAX_THREAD && current_thread->state == RUNNABLE)
    {
        /* The current thread is the only runnable thread; run it. */
        next_thread = current_thread;
    }

    if (next_thread == 0)
    {
        printf(2, "thread_schedule: no runnable threads\n");
        exit();
    }

    if (current_thread != next_thread)
    { /* switch threads?  */
        next_thread->state = RUNNING;
        thread_switch();
    }
    else
        next_thread = 0;
}

void thread_create(void (*func)())
{
    thread_p t;

    for (t = all_thread; t < all_thread + MAX_THREAD; t++)
    {
        if (t->state == FREE)
            break;
    }
    t->sp = (int)(t->stack + STACK_SIZE); // set sp to the top of the stack
    t->sp -= 4;                           // space for return address
    *(int *)(t->sp) = (int)func;          // push return address on stack
    t->sp -= 32;                          // space for registers that thread_switch expects
    t->state = RUNNABLE;
}

void thread_yield(void)
{
    current_thread->state = RUNNABLE;
    thread_schedule();
}

static void
mythread(void)
{
    int i;
    printf(1, "my thread running\n");
    for (i = 0; i < 100; i++)
    {
        printf(1, "my thread 0x%x\n", (int)current_thread);
        thread_yield();
    }
    printf(1, "my thread: exit\n");
    current_thread->state = FREE;
    thread_schedule();
}

int main(int argc, char *argv[])
{
    thread_init();
    thread_create(mythread);
    thread_create(mythread);
    thread_schedule();
    return 0;
}
